using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
/*
	Todo
	adjust next and previous to return a pos, length pair and use that to set the selection
	final test clear out old and comment up new.
*/
namespace DbView
{
	/// <summary>
	/// Summary description for SourceForm.
	/// </summary>
	public partial class SourceForm2 : System.Windows.Forms.Form
	{
		private int maxMatches = 1000; // limit to matching text occurrences (too many is too slow).
		private int maxSourceSizeForColour = 50000; // limit to size of the source in chars for syntax colouring. 50000 obtained by experiment.
		private string m_SearchText;
		private Rectangle m_HoldBounds;

		public SourceForm2(String ItemName, string theProcSource, string searchText)
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			// unselect
			this.theSource.Text += theProcSource;
			if (this.theSource.Text.Length < this.maxSourceSizeForColour)
				theSource.Rtf = this.m_Lex.CreateRtfString(theProcSource, theSource.BackColor);

			this.theSource.SelectionLength = 0;
			// this.theSource.ReadOnly = true;
			// fix up caption
			this.Text = string.Format("Source: [{0}]", ItemName);

			// Status display
			int lines = LineCount(theProcSource);
			this.toolStripStatusLines.Text = lines.ToString();
			this.toolStripStatusChars.Text = theProcSource.Length.ToString();
			// display matches for the search text sent in 
			if (searchText.Length > 0)
			{
				// try a whole item search
				this.SetMatchPoints(theProcSource, searchText, true);
				// if exact found then set exact option on
				if (MatchCount() > 0) this.chkWholeWord.Checked = true;
				this.textSearch.Text = searchText;
				onFind(searchText);
			}
		}

		private void SourceForm2_Shown(object sender, EventArgs e)
		{
			int SourceLinesLargeLimit = 30;
			// auto-enlarge if source is big.
			int lines;
			if (int.TryParse(this.toolStripStatusLines.Text, out lines))
				if (lines > SourceLinesLargeLimit)  // just too many
					btnZoom_Click(this.btnZoom, new EventArgs());

			this.toolStripStatusLines.Text = lines.ToString();
			// KeyBoard UI. Set focus to the search filter 
			this.textSearch.Focus();
		}

		// currently not usable as the dialog is displayed modeless
		public string SearchText { get { return this.m_SearchText; } }

		private void btnClose_Click(object sender, System.EventArgs e) { this.Close(); }

		// set text dirty flag if changed to force a recalc of match points used when navigating
		private void theSource_TextChanged(object sender, EventArgs e) { this.m_IsDisplayChanged = true; }

		private void btnFind_Click(object sender, System.EventArgs e) { onFind(this.textSearch.Text); }

		private void btnNext_Click(object sender, System.EventArgs e) { onNext(true); }

		private void btnPrev_Click(object sender, System.EventArgs e) { onPrev(); }

		private void chkWholeWord_CheckedChanged(object sender, EventArgs e) { onFind(this.textSearch.Text); }
		
		private void textSearch_TextChanged(object sender, EventArgs e)
		{
			 onFind(this.textSearch.Text, false); 
		}
		//private string ModifySource(String source)
		//{
		//    this.SetMatchPoints(source, this.m_SearchText, this.chkWholeWord.Checked);
		//    int hitCount = MatchPoints.GetLength(0);
		//    int adj = (this.chkWholeWord.Checked) ? 1 : 0;
		//    for (int idx = 0; idx < hitCount; ++idx)
		//    {
		//        int element = hitCount - idx - 1;
		//        // add ending markup first
		//        source = source.Insert(MatchPoints[element, 0] + MatchPoints[element, 1] - adj, "\\highlight0\\b0 ");
		//        // make bold and change the colour.
		//        // first apply a bounds adjustment for matches starting with a space otherwise
		//        // the rtf formatter eats it.
		//        source = source.Insert(MatchPoints[element, 0] + adj, "\\b\\highlight2 ");
		//    }
		//    return source.Replace("\n", "\\line\n");
		//}

		//private void onFind(String searchText)
		//{
		//    this.m_SearchText = searchText;
		//    string source = theSource.Text;
		//    // if empty then set text to non-rtf.
		//    if (this.m_SearchText.Length == 0)
		//    {
		//        theSource.Text = source;
		//        this.m_IsDisplayChanged = false; // reset dirty
		//    }
		//    else
		//    {
		//        // convert to rtf 
		//        // (see; http://www.pindari.com/rtf1.html or Google "rtf tutorial")
		//        string rtfHead =
		//            // header
		//            @"{\rtf1\ansi\deff0" + "\n" +
		//            // mark colour
		//            @"{\colortbl;\red0\green0\blue0;\red255\green255\blue064;}" + "\n" +
		//            // font definition
		//            @"{\fonttbl {\f0 Courier new;}}\fs16" + "\n";     // 16 = 8 point

		//        string rtfTail = @"}";  // as per rtf spec
		//        theSource.Rtf = rtfHead + ModifySource(source) + rtfTail;
		//        this.m_IsDisplayChanged = false; // reset dirty
		//        onNext();
		//    }
		//    UpdateStatusBar();
		////}

		enum Modify { encode, decode }
		private string ModifySource(String source, Modify action)
		{
			// select some tags unlikely to cause fals positives.
			string startMatchTag = ">$match$>";
			string endMatchTag = "<$match$<";
			if (action == Modify.encode)
			{
				int hitCount = MatchPoints.GetLength(0);
				int adj = (this.chkWholeWord.Checked) ? 1 : 0;
				adj = 0;
				for (int idx = 0; idx < hitCount; ++idx)
				{
					int element = hitCount - idx - 1;
					// add ending markup first
					source = source.Insert(MatchPoints[element, 0] + MatchPoints[element, 1] - adj, endMatchTag);
					// make bold and change the colour.
					// first apply a bounds adjustment for matches starting with a space otherwise
					// the rtf formatter eats it.
					source = source.Insert(MatchPoints[element, 0] + adj, startMatchTag);
				}
			}
			else
			{
				source = source.Replace(endMatchTag, "\\highlight2\\b0 ");
				source = source.Replace(startMatchTag, "\\b\\highlight3 ");
			}
			return source;
			//return source.Replace("\n", "\\line\n");

		}

		// Legacy version
		private void onFind(String searchText)
		{
			this.onFind(searchText, true);
		}

		// new version
		private void onFind(String searchText, bool setFocus)
		{
			this.m_SearchText = searchText;
			string source = theSource.Text;
			this.SetMatchPoints(source, this.m_SearchText, this.chkWholeWord.Checked);
			// only try to mark source if...
			bool markupSource = source.Length < this.maxSourceSizeForColour &&  // but not too large for colouring and 
								this.MatchCount() < this.maxMatches;			// not too many mnatches (performance)
			if (markupSource)
			{
				source = ModifySource(source, Modify.encode);
				string rtf = this.m_Lex.CreateRtfString(source, theSource.BackColor);
				// replace markup with rtf here.
				rtf = ModifySource(rtf, Modify.decode);
				theSource.Rtf = rtf;
			}

			this.m_IsDisplayChanged = false; // reset dirty
			if (this.m_SearchText.Length > 0)
				onNext(setFocus);

			UpdateStatusBar();
		}

		private void UpdateStatusBar()
		{
			string searchInfo = ""; // if not searching
			if (this.m_SearchText.Length > 0)
				searchInfo = string.Format("Search: {0} matches for {1}", MatchCount(), this.m_SearchText);
			this.toolStripStatusSearchInfo.Text = searchInfo;
		}

		// Navigation 
		private void onNext(bool setFocus)
		{
			onMove(true, setFocus);
		}
		
		private void onPrev()
		{
			onMove(false, true);
		}
		
		// bidirectional core method depends on tf param
		private void onMove(bool forward, bool setFocus)
		{
			// use caret if present
			int pos = theSource.SelectionStart;
			// test for dirty and if so re-calculate the match locations
			if (this.m_IsDisplayChanged) onFind(this.m_SearchText);

			string s = theSource.Text;
			int matchIndex = forward ? FindNext(s, pos) : FindPrev(s, pos);
			// something to do. move the selection
			if (matchIndex >= 0)
			{
				theSource.SelectionStart = MatchPoints[matchIndex, 0];
				theSource.SelectionLength = MatchPoints[matchIndex, 1];
				
				// set focus to highlight current selection
				// this is default. Only not wanted when entering the search text
				if (setFocus)
					theSource.Focus();
				
				theSource.ScrollToCaret();
			}
		}

		private int FindNext(String source, int FromPos)
		{
			int matchCount = MatchPoints.GetLength(0);
			if (matchCount == 0) return -1;
			for (int idx = 0; idx < matchCount; ++idx)
				if (MatchPoints[idx, 0] > FromPos) return idx;
			return 0;
		}

		private int FindPrev(String source, int CurrentSelPos)
		{
			if (CurrentSelPos < 0)
				return -1;
			int matchCount = MatchPoints.GetLength(0);
			if (matchCount == 0) return -1;
			for (int idx = 0; idx < matchCount; ++idx)
			{
				int element = matchCount - idx - 1;
				if (MatchPoints[element, 0] < CurrentSelPos) return element;
			}
			return matchCount - 1;
		}

		private void btnCopy_Click(object sender, System.EventArgs e)
		{
			theSource.SelectAll();
			theSource.Copy();
			this.Close();
		}

		private void btnZoom_Click(object sender, EventArgs e)
		{
			string btnTextEnlarge = "&Enlarge";
			string btnTextRestore = "Restor&e";
			Button zoom = (Button)sender;
			string buttonText = zoom.Text;
			if (buttonText == btnTextRestore) // e n l a r g e
			{
				this.SetBounds(this.m_HoldBounds.Left, this.m_HoldBounds.Top, this.m_HoldBounds.Width, this.m_HoldBounds.Height);
				zoom.Text = btnTextEnlarge;
			}
			else
			{
				// update sizing
				this.m_HoldBounds = this.Bounds;
				// get screen size
				Rectangle scr = Screen.PrimaryScreen.Bounds;
				int bigWidth = scr.Width - scr.Width / 10;  // 90%
				if (bigWidth > 1000) bigWidth = 1000;     // hey: not too big
				int bigHeight = scr.Height - scr.Height / 10;  // 90%

				int topX = (scr.Width - bigWidth) / 2;
				int topY = (scr.Height - bigHeight) / 2;

				// resize window
				this.SetBounds(topX, topY, bigWidth, bigHeight);
				//int margin = 20;
				//this.SetBounds(1, 1, scr.Width - margin, scr.Height - margin);
				zoom.Text = btnTextRestore;
			}
		}

		private void theSource_KeyUp(object sender, KeyEventArgs e)
		{
			if ((Control.ModifierKeys & Keys.Control) == Keys.Control && e.KeyCode == Keys.F)
			{
				// check marked
				string marked = this.theSource.SelectedText.Trim();
				// sanity check. something marked and not spaces in it then aut search
				if (marked.Length > 0 && marked.IndexOf(" ") == -1)
				{
					this.textSearch.Text = marked;
					onFind(marked);
				}
				else
					this.textSearch.Focus();
				e.Handled = true;
			}
			if (e.KeyCode == Keys.F3)
			{
				if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
					this.onPrev();
				else
					this.onNext(true);
				e.Handled = true;
			}
		}

		// create an array of match point pairs using a regex to identify word boundaries
		// number a contains the position, number b the length of the match.
		void SetMatchPoints(String source, string searchText, bool wholeWord)
		{
			// make search term upper as our regex is always case-sensitive (grrr.)
			string tgt = (string.IsNullOrEmpty(searchText)) ? "-{{}-"  // if search text is empty set search to a whacky string intended not to match
															: searchText.ToUpper(); // extract the search from the command
			// if the search text is eg.  coalesce( this will break the regex so the ( needs to be escaped.
			tgt = RegexEscapeControlChars(tgt);
			// build an appropriate Regex
			// matching start of line, space then a bunch of delimiters
			string before = (wholeWord) ? @"(^|[\s|'|.|!|""|%|&|(|*|+|,|\-|||:|;|<|=|>|\^|\[])" : ""; // keep the regex in sync with the version in TableCommandProcessor.cs
			// .. the after adjusted for end of line and closing brackets.
			string after = (wholeWord) ? @"([\s|'|.|!|""|%|&|)|*|+|,|\-|||:|;|<|=|>|\^|\]]|$)" : "";

			string pattern = before + tgt + after;
			SimpleRegex search = new SimpleRegex(pattern);
			// convert t
			string usource = source.ToUpper();
			MatchPoints = search.GetMatches(usource);

			// Adjust when a match contains the word delimeters.
			// slight bug if the match is the very last word in the script and no new line follows
			if (wholeWord)
			{
				for (int i = 0; i < MatchPoints.GetLength(0); ++i)
				{
					MatchPoints[i, 0] += 1;  // move start on by 1 skipping the delimeter
					MatchPoints[i, 1] -= 2;	 // decrease length by 2 for start and end delimeter.
				}
			}
		}

		private string RegexEscapeControlChars(string tgt)
		{
			return tgt.Replace("(", @"\(").Replace(")", @"\)").Replace("^", @"\^").Replace("[", @"\[");
		}

		int LineCount(String text)
		{
			int iPos = text.IndexOf('\n');
			int lineCount = 1;
			while (iPos >= 0)
			{
				++lineCount;
				iPos = text.IndexOf('\n', iPos + 1);
			}
			return lineCount;
		}

		int MatchCount() { return MatchPoints.GetLength(0); }

		int[,] MatchPoints { get; set; }
		bool m_IsDisplayChanged = false;

		// Inherited from the Sql Profiler to colour up the sql syntax
		private readonly ExpressProfiler.YukonLexer m_Lex = new ExpressProfiler.YukonLexer();



	}
}
